﻿// Spun AJAX Framework
// Copyright (C) 2008 Blake Hegerle
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Spun.Utility.Locking;

namespace Spun.Models
{
    public class ModelContext
    {
        public ModelContext()
            : this(new LockingContext())
        { }

        public ModelContext(LockingContext LockingContext)
        {
            ReferenceMap = new Dictionary<ModelBase, ModelReferences>();

            this.LockingContext = LockingContext;
        }

        public T New<T>() where T : class
        {
            ProxyModel<T> Model = ModelFactory<T>.New();
            Register(Model);
            return Model.ProxyInterface;
        }

        public void Register(ModelBase NewModel)
        {
            NewModel.MemberChangedEvent += OnMemberDidChange;
        }

        public IEnumerable<ModelBase> ReferencesTo(ModelBase Model)
        {
            using (LockingContext.ReadLock)
                if (ReferenceMap.ContainsKey(Model))
                    return new List<ModelBase>(ReferenceMap[Model].ReferencesTo);
                else
                    return new List<ModelBase>();
        }

        public IEnumerable<ModelBase> ReferencesFrom(ModelBase Model)
        {
            using (LockingContext.ReadLock)
                if (ReferenceMap.ContainsKey(Model))
                    return new List<ModelBase>(ReferenceMap[Model].ReferencesFrom);
                else
                    return new List<ModelBase>();
        }

        private void OnMemberDidChange(ModelBase UpdatedModel, string MemberName, object OldValue, object NewValue)
        {
            using (LockingContext.WriteLock)
            {
                if (OldValue != null && OldValue is ModelBase)
                    RemoveReference(UpdatedModel, OldValue as ModelBase);
                if (NewValue != null && NewValue is ModelBase)
                    AddReference(UpdatedModel, NewValue as ModelBase);
            }
        }

        private void AddReference(ModelBase Owner, ModelBase Referant)
        {
            AddReference(References(Owner).ReferencesFrom, Referant);
            AddReference(References(Referant).ReferencesTo, Owner);
        }

        private void AddReference(List<ModelBase> list, ModelBase Referant)
        {
            if (list.Contains(Referant) == false)
                list.Add(Referant);
        }

        private void RemoveReference(ModelBase Owner, ModelBase Referant)
        {
            RemoveReference(References(Owner).ReferencesFrom, Referant);
            RemoveReference(References(Referant).ReferencesTo, Owner);
        }

        private void RemoveReference(List<ModelBase> list, ModelBase Referant)
        {
            list.RemoveAll(x => x == Referant);
        }

        private ModelReferences References(ModelBase Model)
        {
            if (ReferenceMap.ContainsKey(Model) == false)
                ReferenceMap[Model] = new ModelReferences();
            return ReferenceMap[Model];
        }

        private class ModelReferences
        {
            public readonly List<ModelBase> ReferencesFrom = new List<ModelBase>();
            public readonly List<ModelBase> ReferencesTo = new List<ModelBase>();
        }

        private LockingContext LockingContext;
        private Dictionary<ModelBase, ModelReferences> ReferenceMap;
    }
}
